<?php

    #-------------------------------------------------------------#
	# This file is part of the Tekuna MVC Microframework for PHP. #
	# (c) 2009 Niels Jäckel <niels.jaeckel@tekuna.org>            #
	# Please visit http://tekuna.org/ for more information.       #
	#                                                             #
	# This software is licensed under the MIT License. Please     #
	# view the LICENSE file for the full text of this license.    #
    #-------------------------------------------------------------#


	Tekuna :: load('org.tekuna.core.mvc.TekunaMVCController');
	Tekuna :: load('org.tekuna.core.mvc.controller.AbstractController');
	Tekuna :: load('org.tekuna.core.mvc.controller.ControllerRedirectException');
	Tekuna :: load('org.tekuna.core.mvc.controller.ControllerValidationException');
	Tekuna :: load('org.tekuna.core.application.TekunaApplicationContext');
	Tekuna :: load('org.tekuna.core.i18n.TekunaTranslator');
	Tekuna :: load('org.tekuna.core.i18n.DefaultTranslator');
	Tekuna :: load('org.tekuna.core.mvc.TekunaView');
	Tekuna :: load('org.tekuna.core.mvc.view.SimpleView');
	Tekuna :: load('org.tekuna.core.mvc.view.PHPView');
	Tekuna :: load('org.tekuna.core.mvc.view.XSLTView');
	Tekuna :: load('org.tekuna.core.mvc.TekunaModel');
	Tekuna :: load('org.tekuna.core.request.Request');
	Tekuna :: load('org.tekuna.core.response.Response');
	Tekuna :: load('org.tekuna.core.request.URLInformation');
	Tekuna :: load('org.tekuna.core.request.ClientInformation');


	/**
	 * The AbstractMVCController is the default implementation of the TekunaMVCController.
	 * It provides the basic TekunaController features and some other things:
	 *
	 *   - simple output workflow (display(), validate() and store() methods)
	 *   - global exception handling (error() method)
	 *   - implicit View instanciation
	 *   - implicit Model instanciation
	 *   - implicit translation of the generated output
	 *   - simple configuration by subclassing and overwriting of class members
	 */

	abstract class AbstractMVCController extends AbstractController implements TekunaMVCController {

		protected
			$objView = NULL,
			$objModel = NULL,
			$objTranslator = NULL,

			// these attributes are overwritable by the subclasses
			/**
			 * The class name of the view engine that is instanciated implicitly.
			 *
			 * This value must be overwritten to adjust the facilities of the controller.
			 */

			$sViewClass = 'SimpleView',



			/**
			 * A flag that defines, if the automatic translation process is triggered after the
			 * output was generated. Set this to false to prevent output translation with the
			 * language-dependent .ini files.
			 *
			 * This value must be overwritten to adjust the facilities of the controller.
			 */

			$bTranslateOutput = TRUE,



			/**
			 * A flag that defines, if the default model should be loaded and assigned as
			 * used model object.
			 *
			 * This value must be overwritten to adjust the facilities of the controller.
			 */

			$bAutoloadModel = FALSE;


		public function __construct(TekunaApplicationContext $objContext) {

			// call the parent constructor
			parent :: __construct($objContext);

			// init associated objects
			$this -> init();
		}


		protected function init() {

			// init the translator instance
			$sLanguageFile = APPLICATION_ROOT .
			                 TekunaApplication :: BASEDIR_LANGFILES .
							 $this -> getApplicationContext() -> getApplicationLanguage() .
			                 DIRECTORY_SEPARATOR .
			                 $this -> getDefaultLanguageFile();

			$this -> useTranslator(new DefaultTranslator($this -> getApplicationContext() -> getApplicationLanguage(), $sLanguageFile));


			// init the view
			$sTemplateBaseDir = APPLICATION_ROOT .
			                    TekunaApplication :: BASEDIR_COMPONENTS .
			                    'view' .
			                    DIRECTORY_SEPARATOR;

			$sTemplate = $this -> getDefaultTemplate();

			// instantiate view
			$objView = new $this -> sViewClass($sTemplateBaseDir, $sTemplate);
			$this -> useView($objView);


			// load model automatically if configured so
			if ($this -> bAutoloadModel) {

				$this -> autoloadModel();
			}
		}


		/**
		 * Enhanced output generation method. It loads the configured view, optionally the
		 * default model and runs the controller workflow. After that, the view is processed
		 * and optionally translated.
		 *
		 * If any subsequent controller redirects occured, the result of the last redirected
		 * controller is returned.
		 */

		public function getOutput() {

			try {

				// run the controller workflow
				$this -> runControllerWorkflow($this -> getApplicationContext() -> getRequest(), $this -> objView);

				// process the view
				$sOutput = $this -> objView -> process();

				// translate output if enabled
				if ($this -> bTranslateOutput) {

					$sOutput = $this -> getTranslator() -> translate($sOutput);
				}

				// return controller output if not redirected
				return $sOutput;
			}
			catch (ControllerRedirectException $objContainer) {

				// extract the output from the redirect exception
				return $objContainer -> getControllerOutput();
			}
		}


		protected function getDefaultTemplate() {

			// determine current instance type
			$sClassName = get_class($this);

			// return specific template name
			switch ($this -> sViewClass) {

				case 'PHPView': return "$sClassName.php";
				case 'XSLTView': return "$sClassName.xsl";

				default: return "$sClassName.tpl";
			}
		}


		protected function getDefaultLanguageFile() {

			return get_class($this) . '.ini';
		}


		protected function runControllerWorkflow(Request $objRequest, TekunaView $objView) {

			try {

				if ($objRequest -> isGETRequest()) {

					// just display the page for GET requests
					$this -> display($objRequest, $objView);
				}
				else {

					// for POST requests first call the validation
					if ($this -> validate($objRequest, $objView)) {

						$this -> store($objRequest, $objView);
					}
					else {

						$sClassName = get_class($this);
						throw new ControllerValidationException("Validation failed for controller '$sClassName'.");
					}
				}
			}
			catch (Exception $e) {

				// do not handle redirect exceptions
				if ($e instanceof ControllerRedirectException) {
					
					throw $e;
				}
				else {

					// call error for all other exceptions
					$this -> error($objRequest, $objView, $e);
				}
			}
		}


		/**
		 * Abstract method hook, that is called, if the controller is executed
		 * with a GET request.
		 *
		 * @param Request $objRequest the current request
		 * @param TekunaView $objView the target view
		 */

		abstract public function display(Request $objRequest, TekunaView $objView);


		/**
		 * Default method hook, that is called, if the controller is executed
		 * with a POST request. If the method returns FALSE or NULL, a
		 * ControllerValidationException is thrown, that is handled by the error() method.
		 * If the validation succeeds (the method returns TRUE), the store() method
		 * will be called.
		 *
		 * This default implementation just returns TRUE. If your controller
		 * should store any data after a POST request, you must overwrite
		 * this method with your validation code.
		 *
		 * @param Request $objRequest the current request
		 * @param TekunaView $objView the target view
		 * @return boolean validation success
		 */

		public function validate(Request $objRequest, TekunaView $objView) {

			return TRUE;
		}



		/**
		 * Default method hook, that is called, if the controller is executed
		 * with a POST request and the validate() method succeeds.
		 *
		 * This default method just calls the display() method and so triggers
		 * the default presentation logic. If youre controller should store any
		 * data after a POST request, you must overwrite this method with your
		 * special storage code (usually some model interaction).
		 *
		 * @param Request $objRequest the current request
		 * @param TekunaView $objView the target view
		 */

		public function store(Request $objRequest, TekunaView $objView) {

			$this -> display($objRequest, $objView);
		}


		/**
		 * This method is activated, if any exception is thrown within the controller
		 * workflow process. The implemented default behavior is to throw the received
		 * exception again, so that it will be handled by the TekunaApplication.
		 *
		 * Overwrite this method to adjust this behavior and display a custom error page or
		 * just redirect to the display() method and show a nice error method.
		 *
		 * @param Request $objRequest the current request
		 * @param TekunaView $objView the target view
		 * @param Exception $objException the previously catched exception
		 */

		public function error(Request $objRequest, TekunaView $objView, Exception $objException) {

			// throw again to trigger default handler in the TekunaApplication class
			throw $objException;
		}


		protected function autoloadModel() {

			// prepend common model 'package'
			$sModelClass = get_class($this) .'Model';
			$sFullModelClass = 'components.model.'. $sModelClass;

			// load the controller class if not yet loaded
			// check performed without calling __autoload (false parameter)
			if (! class_exists($sModelClass, false)) {

				TekunaApplication :: load($sFullModelClass);
			}

			// instantiate model
			$this -> objModel = new $sModelClass();

			// check the model type
			if (! $this -> objModel instanceof TekunaModel) {

				throw new TekunaApplicationException("The class '$sModelClass' must implement the TekunaModel interface.");
			}
		}


		/**
		 * Redirect to another controller and take its output
		 * in place of the output of this controller
		 *
		 * @param string $sControllerSpecification
		 *        the controller specification that points to another
		 *        controller in the application scope. For more information
		 *        see the TekunaApplication :: getApplicationControllerInstance() method
		 */

		public function redirect($sControllerSpecification) {

			// get an instance of the controller
			$objController = TekunaApplication :: getControllerInstance($sControllerSpecification, $this -> getApplicationContext());

			// get the output of the other controller
			$sOutput = $objController -> getOutput();

			// break the control flow
			throw new ControllerRedirectException($sOutput);
		}


		/**
		 * Set the view that is used with this controller
		 *
		 * @param TekunaView $objView the new view
		 */

		public function useView(TekunaView $objView) {

			$this -> objView = $objView;
		}


		/**
		 * Set the translator that is used to translated the output of the view
		 *
		 * @param TekunaTranslator $objTranslator the new translator
		 */

		public function useTranslator(TekunaTranslator $objTranslator) {

			$this -> objTranslator = $objTranslator;
		}


		/**
		 * Set the model that is used with this controller
		 *
		 * @param TekunaModel $objModel the new model
		 */

		public function useModel(TekunaModel $objModel) {

			$this -> objModel = $objModel;
		}


		/**
		 * @return TekunaView returns the currently used view
		 */

		public function getView() {

			return $this -> objView;
		}


		/**
		 * @return TekunaTranslator returns the current translator instance
		 */

		public function getTranslator() {

			return $this -> objTranslator;
		}


		/**
		 * @return TekunaModel returns the currently used model
		 */

		public function getModel() {

			return $this -> objModel;
		}
	}

